﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Xml;
#if L2S
using System.Data.Linq;
using System.Data.Linq.Mapping;
#endif

namespace ALinq.Mapping
{
    /// <summary>
    /// Represents a mapping source that uses fluent api to create the mapping model.
    /// </summary>
    /// <example>
    /// <code lang="c#">
    /// <![CDATA[
    ///class NorthwindMappingSource : FluentMappingSource
    ///{
    ///    public NorthwindMappingSource(Action<DatabaseMapping<NorthwindDatabase>> func = null)
    ///    {
    ///        Map<NorthwindDatabase>(mapping =>
    ///        {
    ///            mapping.ProviderType = typeof(AccessDbProvider);
    ///            mapping.Table(o => o.Categories, CategoryTableName)
    ///                   .PrimaryKey(o => o.CategoryID)
    ///                   .Column(o => o.CategoryName)
    ///                   .Column(o => o.Description)
    ///                   .Column(o => o.Picture)
    ///                   .Association(o => o.Products, o => new { o.CategoryID }, o => new { o.CategoryID });
    ///
    ///            mapping.Table(o => o.Products, ProdcutTableName)
    ///                   .PrimaryKey(o => o.ProductID)
    ///                   .Column(o => o.ProductName)
    ///                   .Column(o => o.CategoryID)
    ///                   .Association(o => o.Category, o => o.CategoryID, o => o.CategoryID);
    ///
    ///            if (func != null)
    ///                func(mapping);
    ///        });
    ///    }
    ///}
    /// ]]>
    /// </code>
    /// <code lang="c#">
    /// <![CDATA[
    /// class NorthwindMappingSource : FluentMappingSource
    /// {
    ///     public NorthwindMappingSource(Action<DatabaseMapping> func = null)
    ///     {
    ///         this.Map<NorthwindDatabase>(mapping =>
    ///         {
    ///             mapping.ProviderType = typeof(AccessDbProvider);
    ///             mapping.Name = typeof(NorthwindDatabase).Name;
    ///             mapping.Table(o => o.Contacts, "Contacts", o => { o.InheritanceCode = "Unknow"; o.IsInheritanceDefault = true; })
    ///                     .PrimaryKey(o => o.ContactID, o => { o.Storage = "_ContactID"; })
    ///                     .Column(o => o.ContactType, o => o.IsDiscriminator = true)
    ///                     .Column(o => o.CompanyName)
    ///                     .Column(o => o.Phone)
    ///                     .Column(o => o.GUID)
    ///                     .Inheritance<FullContact>()
    ///                     .Column(o => o.ContactName)
    ///                     .Column(o => o.ContactTitle)
    ///                     .Column(o => o.Address)
    ///                     .Column(o => o.City)
    ///                     .Column(o => o.Region)
    ///                     .Column(o => o.PostalCode)
    ///                     .Column(o => o.Country)
    ///                     .Column(o => o.Fax)
    ///                     .Inheritance<SupplierContact>()
    ///                     .Column(o => o.HomePage)
    ///                     .Inheritance<EmployeeContact>()
    ///                     .Column(o => o.PhotoPath)
    ///                     .Column(o => o.Photo)
    ///                     .Column(o => o.Extension)
    ///                     .Inheritance<CustomerContact>()
    ///                     .Inheritance<ShipperContact>();
    ///
    ///             if (func != null)
    ///                 func(mapping);
    ///         });
    ///     }
    /// }
    /// ]]>
    /// </code>
    /// </example>
    public class FluentMappingSource : MappingSource
    {
        private Dictionary<Type, XmlMappingSource> xmlMappingSources;

        ///<summary>
        /// Initializes a new instance of the ALinq.Mapping.FluentMappingSource class.
        ///</summary>
        public FluentMappingSource()
        {
            xmlMappingSources = new Dictionary<Type, XmlMappingSource>();
        }

        /// <summary>
        /// Create a mapping between the data-context type into the database.
        /// </summary>
        /// <typeparam name="TContext">Type of the data-context.</typeparam>
        /// <param name="func">Function to build the database-mapping.</param>
        public void Map<TContext>(Action<DatabaseMapping<TContext>> func)
           where TContext : DataContext
        {
            Error.ValidateArgumentNotNull(() => func);

            XmlMappingSource xmlMappingSource;
            if (xmlMappingSources.TryGetValue(typeof(TContext), out xmlMappingSource))
                throw Error.MappingExists(typeof(TContext));

            //XmlDocument doc = new XmlDocument();
            var mapping = new DatabaseMapping<TContext>();
            func(mapping);

            var sb = new StringBuilder();
            var writer = XmlWriter.Create(sb);
            var doc = DatabaseMappingSerializer.Serialize(mapping);
            doc.WriteTo(writer);
            writer.Flush();

            this.xmlMappingSources[typeof(TContext)] = XmlMappingSource.FromXml(sb.ToString());
        }

        /// <summary>
        /// Creates a new mapping model.
        /// </summary>
        /// <param name="dataContextType">The type of ALinq.DataContext on which to base the mapping.</param>
        /// <returns>The meta-model created to match the current mapping scheme.</returns>
        protected override MetaModel CreateModel(Type dataContextType)
        {
            XmlMappingSource xmlMappingSource;

            if (!xmlMappingSources.TryGetValue(dataContextType, out xmlMappingSource))
                throw Error.MetaModelNotExists(dataContextType);

            var model = xmlMappingSource.GetModel(dataContextType);
            return model;
        }

    }
}
